﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;

namespace SBPweb.Forge.Collections
{

    public class ObservableDictionary<TKey, TItem> : ObservableList<KeyValuePair<TKey, TItem>>, IDictionary<TKey, TItem>
    {

        #region Private fields

        private IDictionary<TKey, int> itemIndexes;

        #endregion

        #region Constructor logic

        public ObservableDictionary()
            : base()
        {
            itemIndexes = new Dictionary<TKey, int>();
        }

        public ObservableDictionary(int capacity)
            : base(capacity)
        {
            itemIndexes = new Dictionary<TKey, int>(capacity);
        }

        public ObservableDictionary(IEqualityComparer<TKey> comparer)
            : base()
        {
            itemIndexes = new Dictionary<TKey, int>(comparer);
        }
        
        public ObservableDictionary(int capacity, IEqualityComparer<TKey> comparer)
            : base(capacity)
        {
            itemIndexes = new Dictionary<TKey, int>(capacity, comparer);
        }

        public ObservableDictionary(IDictionary<TKey, TItem> items)
            : base(items)
        {
            itemIndexes = new Dictionary<TKey, int>(items.Count);
            InitializeIndexes();
        }

        public ObservableDictionary(IDictionary<TKey, TItem> items, IEqualityComparer<TKey> comparer)
            : base(items)
        {
            itemIndexes = new Dictionary<TKey, int>(items.Count, comparer);
            InitializeIndexes();
        }

        #endregion

        #region Property accessors

        public ICollection<TKey> Keys
        {
            get
            {
                lock (itemIndexes)
                {
                    return itemIndexes.Keys;
                }
            }
        }

        public ICollection<TItem> Values
        {
            get
            {
                lock (itemIndexes)
                {
                    ICollection<TItem> ret = new Collection<TItem>();
                    foreach (KeyValuePair<TKey, TItem> kvp in this)
                    {
                        ret.Add(kvp.Value);
                    }
                    return ret;
                }
            }
        }

        public TItem this[TKey key]
        {
            get
            {
                return base[IndexOfKey(key)].Value;
            }
            set
            {
                int index = IndexOfKey(key, false);
                KeyValuePair<TKey, TItem> kvp = new KeyValuePair<TKey, TItem>(key, value);
                if (index == -1)
                {
                    AddItem(kvp);
                }
                else
                {
                    SetItem(index, kvp);
                }
            }
        }

        #endregion

        #region Methods

        public void Add(TKey key, TItem value)
        {
            this.AddItem(new KeyValuePair<TKey, TItem>(key, value));
        }

        public bool ContainsKey(TKey key)
        {
            lock (itemIndexes)
            {
                return itemIndexes.ContainsKey(key);
            }
        }

        public bool Remove(TKey key)
        {
            int i = 0;
            try
            {
                i = IndexOfKey(key);
                this.RemoveAt(i);
            }
            catch
            {
                return false;
            }
            return true;
        }

        public bool TryGetValue(TKey key, out TItem value)
        {
            int i = 0;
            try
            {
                i = IndexOfKey(key);
                value = base[i].Value;
            }
            catch
            {
                value = default(TItem);
                return false;
            }
            return true;
        }

        protected virtual int IndexOfKey(TKey key)
        {
            return IndexOfKey(key, true);
        }

        protected virtual int IndexOfKey(TKey key, bool throwError)
        {
            lock (itemIndexes)
            {
                int i = -1;
                if (itemIndexes.ContainsKey(key))
                {
                    i = itemIndexes[key];
                }
                if (i < 0 && throwError)
                {
                    throw new KeyNotFoundException();
                }
                return i;
            }
        }

        #region Overridden

        protected override void AddItem(KeyValuePair<TKey, TItem> item)
        {
            lock (itemIndexes)
            {
                TKey key = item.Key;

                if (itemIndexes.ContainsKey(key))
                {
                    int i = itemIndexes[key];
                    base.SetItem(i, item);
                }
                else
                {
                    base.AddItem(item);
                    int i = base.IndexOf(item);
                    itemIndexes.Add(item.Key, i);
                }
            }
        }

        protected override void ClearItems()
        {
            lock (itemIndexes)
            {
                base.ClearItems();
                itemIndexes.Clear();
            }
        }

        protected override void InsertItem(int index, KeyValuePair<TKey, TItem> item)
        {
            lock (itemIndexes)
            {
                base.InsertItem(index, item);
                itemIndexes[item.Key] = index;
            }
        }

        protected override void RemoveItem(KeyValuePair<TKey, TItem> item)
        {
            lock (itemIndexes)
            {
                base.RemoveItem(item);
                itemIndexes.Remove(item.Key);
            }
        }

        protected override void SetItem(int index, KeyValuePair<TKey, TItem> item)
        {
            lock (itemIndexes)
            {
                TKey key = item.Key;
                base.SetItem(index, item);
                if (itemIndexes.ContainsKey(key))
                {
                    itemIndexes[key] = index;
                }
            }
        }

        public override void Sort()
        {
            base.Sort();
            InitializeIndexes();
        }

        public override void Sort(Comparison<KeyValuePair<TKey, TItem>> comparison)
        {
            base.Sort(comparison);
            InitializeIndexes();
        }

        public override void Sort(IComparer<KeyValuePair<TKey, TItem>> comparer)
        {
            base.Sort(comparer);
            InitializeIndexes();
        }

        public override void Sort(int index, int count, IComparer<KeyValuePair<TKey, TItem>> comparer)
        {
            base.Sort(index, count, comparer);
            InitializeIndexes();
        }

        #endregion

        protected void InitializeIndexes()
        {
            lock (itemIndexes)
            {
                itemIndexes.Clear();
                int i = 0;
                foreach (KeyValuePair<TKey, TItem> kvp in this)
                {
                    itemIndexes.Add(kvp.Key, i++);
                }
            }
        }

        #endregion

    }

}